﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Web.Script.Serialization;

namespace T.Rest
{
    public static class Validators
    {
        private static Func<JavaScriptSerializer, string, Dictionary<string, object>> deserializeSingleObject = (serializer, str) =>
                                                            {
                                                                try
                                                                {
                                                                    return serializer.Deserialize<Dictionary<string, object>>(str);
                                                                }
                                                                catch(Exception)
                                                                {
                                                                    if (str.Substring(0, 1).Equals("["))
                                                                        throw new ArgumentException(string.Format("Deserialization error. Returnvalue is an array\n\n{0}\n\nIs ExpectArrayResult set correctly in configuration?", str));

                                                                    throw new ArgumentException("Unable to deserialize string to a Dictionary<string, object>" + str);
                                                                }

                                                            };
        private static Func<JavaScriptSerializer, string, Dictionary<string, object>> deserializeArray = (serializer, str) =>
                                                            {                                                                                                                                                                                                                                      try
                                                                {
                                                                    //JSON = {[ { "latitude":57.7973333, "longitude":12.0502107, "title":"Angered", "content":"Representing :)" }, { "latitude":57.6969943, "longitude":11.9865, "title":"Gothenburg", "content":"Swedens second largest city" } ]}
                                                                    var takeOne = serializer.Deserialize<List<Dictionary<string, object>>>(str).FirstOrDefault();
                                                                    if (takeOne != null)
                                                                        return takeOne;

                                                                    //JSON = {"markers":[ { "latitude":57.7973333, "longitude":12.0502107, "title":"Angered", "content":"Representing :)" }, { "latitude":57.6969943, "longitude":11.9865, "title":"Gothenburg", "content":"Swedens second largest city" } ]}
                                                                    var takeTwo = serializer.Deserialize<Dictionary<string, object>>(str).FirstOrDefault().Value;
                                                                    if (takeTwo != null)
                                                                    {
                                                                        var v = takeTwo as ArrayList;
                                                                        if (v != null)
                                                                            return v[0] as Dictionary<string, object>;
                                                                    }
                                                                    
                                                                    return null;
                                                                }
                                                                catch(Exception)
                                                                {
                                                                    if (!str.Substring(0, 1).Equals("["))
                                                                        throw new ArgumentException(string.Format("Error during deserialization. Returnvalue is an object\n\n{0}\n\nIs ExpectArrayResult set correctly in configuration?", str));

                                                                    throw new ArgumentException("Unable to deserialize string to a List<Dictionary<string, object>>: " + str);
                                                                }   
                                                            };
        /// <summary>
        /// Validation of returned object(s) from the server vs. expected result declared in RestConfiguration
        /// </summary>
        public static Dictionary<string, object> ValidateSignature(this Ressource restRessource)
        {
            if (restRessource.Configuration.ExpectedObjectSignature == null)
                throw new ArgumentException("ExpectedObjectSignature is null");

            var func = restRessource.Configuration.ExpectArrayResult ? deserializeArray : deserializeSingleObject;            
            
            try
            {
                var result = new ResRequest<Dictionary<string, object>>(restRessource).Parse(func);                
                ValidateSignature(restRessource, result, restRessource.Configuration.ExpectedObjectSignature, restRessource.Configuration.Method, restRessource.Configuration.Url, restRessource.Configuration.QueryString);
                return result;
            }
            catch(Exception ex)
            {
                restRessource.AssertFail(ex.Message);
            }
            return null;
        }

        private static void ValidateSignature(Ressource ressource, Dictionary<string, object> result, Dictionary<string, Type> expectedResult, string method, string url, string queryString)
        {                                    
            if (result == null || result.Count == 0)
                ressource.AssertFail(string.Format("{0} {1}?{2} returned an empty object or array", method, url, queryString));

            var props = (from p in result
                    select new { p.Key, Value = p.Value != null ? p.Value.GetType() : typeof(object) })  //typeof(object) to support null-values in JSON
                .ToDictionary(x => x.Key, x => x.Value);
            
            ValidateObjectProperties(ressource, props, expectedResult);
        }

        private static void ValidateObjectProperties(Ressource ressource, Dictionary<string, Type> propsFromService, Dictionary<string, Type> expectedObjectSignature)
        {            
            ressource.AssertAreNotEqualInts(0, propsFromService.Count(), "No properties returned");
            
            if (expectedObjectSignature.Count() != propsFromService.Count)
            {
                Trace.WriteLine("Properties found in ressource");
                foreach (KeyValuePair<string, Type> prop in propsFromService)
                    Trace.WriteLine(prop.Key + ": " + prop.Value);
            }
            ressource.AssertAreEqualInts(expectedObjectSignature.Count(), propsFromService.Count(), "Antal properties ikke ens");


            var failedProps = new List<string>();
            foreach (KeyValuePair<string, Type> prop in propsFromService)
            {
                var propFound = expectedObjectSignature.FirstOrDefault(x => x.Key.Equals(prop.Key));
                if (string.IsNullOrEmpty(propFound.Key))
                    failedProps.Add(string.Format("Property {0} not expected on {1} {2} ", prop.Key, ressource.Configuration.Method, ressource.Configuration.Url));
                //ressource.AssertAreNotEqualBoolean(true, string.IsNullOrEmpty(propFound.Key), string.Format("Property {0} not expected on {1} {2} ", prop.Key, ressource.Configuration.Method, ressource.Configuration.Url));
                
                if (!propFound.Equals(default(KeyValuePair<string, Type>)))
                {
                    if (!expectedObjectSignature[propFound.Key].Equals(prop.Value) && !prop.Value.Equals(typeof(Object)))  //If property value in returned JSON is null, the type will be System.Object
                        failedProps.Add(string.Format("Type of property {0} does not match expectation, expected {1}, actual {2}", prop.Key, expectedObjectSignature[propFound.Key], prop.Value));
                    //ressource.AssertAreEqualTypes(expectedObjectSignature[propFound.Key], prop.Value, "Type of property " + prop.Key + " does not match expectation");
                }
            }

            if (failedProps.Count > 0)
                ressource.AssertFail("\n\n" + failedProps.Aggregate((x, y) => x + "\n" + y));
        }   
    }
}